package com.gitee.wsl.transform.encode.base

import com.gitee.wsl.io.encodeToByteArray
import com.gitee.wsl.io.write
import kotlinx.io.Buffer
import kotlinx.io.readByteArray
import kotlin.math.ceil
import kotlin.math.ln

/**
 * Base62编码解码实现，常用于短URL<br></br>
 * From https://github.com/seruco/base62
 *
 * @author Looly, Sebastian Ruhleder, sebastian@seruco.io
 * @since 4.5.9
 */
object Base62{

    // -------------------------------------------------------------------- encode
    /**
     * Base62编码
     *
     * @param source 被编码的Base62字符串
     * @return 被加密后的字符串
     */
    fun encodeToString(source: CharSequence): String {
        return encode(source.encodeToByteArray()).decodeToString()
    }

    fun encode(source: CharSequence): ByteArray {
        return encode(source.encodeToByteArray())
    }

    /**
     * Base62编码（反转字母表模式）
     *
     * @param source 被编码的Base62字符串
     * @return 被加密后的字符串
     */
    fun encodeInvertedToString(source: CharSequence): String {
        return encode(source.encodeToByteArray(), true).decodeToString()
    }

    /**
     * 编码指定消息bytes为Base62格式的bytes
     *
     * @param data 被编码的消息
     * @return Base62内容
     */
    fun encode(data: ByteArray): ByteArray {
        return encode(data, false)
    }

    /**
     * 编码指定消息bytes为Base62格式的bytes
     *
     * @param data        被编码的消息
     * @param useInverted 是否使用反转风格，即将GMP风格中的大小写做转换
     * @return Base62内容
     */
    fun encode(data: ByteArray, useInverted: Boolean): ByteArray {
        val encoder = if (useInverted) Base62Encoder.Companion.INVERTED_ENCODER else Base62Encoder.Companion.GMP_ENCODER
        return encoder.encode(data)
    }

    /**
     * Base62解码
     *
     * @param source 被解码的Base62字符串
     * @return 被加密后的字符串
     */
    fun decodeToString(source: CharSequence): String {
        return decode(source.encodeToByteArray()).decodeToString()
    }

    fun decode(source: CharSequence): ByteArray {
        return decode(source.encodeToByteArray())
    }

    /**
     * Base62解码（反转字母表模式）
     *
     * @param source 被解码的Base62字符串
     * @return 被加密后的字符串
     */
    fun decodeInvertedToString(source: CharSequence): String {
        return decode(source.encodeToByteArray(),true).decodeToString()
    }

    /**
     * 解码Base62消息
     *
     * @param encoded Base62内容
     * @return 消息
     */
    fun decode(encoded: ByteArray): ByteArray {
        return decode(encoded, false)
    }

    /**
     * 解码Base62消息
     *
     * @param encoded     Base62内容
     * @param useInverted 是否使用反转风格，即将GMP风格中的大小写做转换
     * @return 消息
     */
    fun decode(encoded: ByteArray, useInverted: Boolean): ByteArray {
        val decoder = if (useInverted) Base62Decoder.Companion.INVERTED_DECODER else Base62Decoder.Companion.GMP_DECODER
        return decoder.decode(encoded)
    }

    /**
     * Base62编码器
     *
     * @since 5.8.0
     */
    class Base62Encoder(private val alphabet: ByteArray)  {
        fun encode(data: ByteArray): ByteArray {
            val indices = convert(data, STANDARD_BASE, TARGET_BASE)
            return translate(indices, alphabet)
        }

        companion object {
            /**
             * GMP风格
             */
            val GMP = byteArrayOf( //
                '0'.code.toByte(),
                '1'.code.toByte(),
                '2'.code.toByte(),
                '3'.code.toByte(),
                '4'.code.toByte(),
                '5'.code.toByte(),
                '6'.code.toByte(),
                '7'.code.toByte(),  //
                '8'.code.toByte(),
                '9'.code.toByte(),
                'A'.code.toByte(),
                'B'.code.toByte(),
                'C'.code.toByte(),
                'D'.code.toByte(),
                'E'.code.toByte(),
                'F'.code.toByte(),  //
                'G'.code.toByte(),
                'H'.code.toByte(),
                'I'.code.toByte(),
                'J'.code.toByte(),
                'K'.code.toByte(),
                'L'.code.toByte(),
                'M'.code.toByte(),
                'N'.code.toByte(),  //
                'O'.code.toByte(),
                'P'.code.toByte(),
                'Q'.code.toByte(),
                'R'.code.toByte(),
                'S'.code.toByte(),
                'T'.code.toByte(),
                'U'.code.toByte(),
                'V'.code.toByte(),  //
                'W'.code.toByte(),
                'X'.code.toByte(),
                'Y'.code.toByte(),
                'Z'.code.toByte(),
                'a'.code.toByte(),
                'b'.code.toByte(),
                'c'.code.toByte(),
                'd'.code.toByte(),  //
                'e'.code.toByte(),
                'f'.code.toByte(),
                'g'.code.toByte(),
                'h'.code.toByte(),
                'i'.code.toByte(),
                'j'.code.toByte(),
                'k'.code.toByte(),
                'l'.code.toByte(),  //
                'm'.code.toByte(),
                'n'.code.toByte(),
                'o'.code.toByte(),
                'p'.code.toByte(),
                'q'.code.toByte(),
                'r'.code.toByte(),
                's'.code.toByte(),
                't'.code.toByte(),  //
                'u'.code.toByte(),
                'v'.code.toByte(),
                'w'.code.toByte(),
                'x'.code.toByte(),
                'y'.code.toByte(),
                'z'.code.toByte() //
            )

            /**
             * 反转风格，即将GMP风格中的大小写做转换
             */
            val INVERTED = byteArrayOf( //
                '0'.code.toByte(),
                '1'.code.toByte(),
                '2'.code.toByte(),
                '3'.code.toByte(),
                '4'.code.toByte(),
                '5'.code.toByte(),
                '6'.code.toByte(),
                '7'.code.toByte(),  //
                '8'.code.toByte(),
                '9'.code.toByte(),
                'a'.code.toByte(),
                'b'.code.toByte(),
                'c'.code.toByte(),
                'd'.code.toByte(),
                'e'.code.toByte(),
                'f'.code.toByte(),  //
                'g'.code.toByte(),
                'h'.code.toByte(),
                'i'.code.toByte(),
                'j'.code.toByte(),
                'k'.code.toByte(),
                'l'.code.toByte(),
                'm'.code.toByte(),
                'n'.code.toByte(),  //
                'o'.code.toByte(),
                'p'.code.toByte(),
                'q'.code.toByte(),
                'r'.code.toByte(),
                's'.code.toByte(),
                't'.code.toByte(),
                'u'.code.toByte(),
                'v'.code.toByte(),  //
                'w'.code.toByte(),
                'x'.code.toByte(),
                'y'.code.toByte(),
                'z'.code.toByte(),
                'A'.code.toByte(),
                'B'.code.toByte(),
                'C'.code.toByte(),
                'D'.code.toByte(),  //
                'E'.code.toByte(),
                'F'.code.toByte(),
                'G'.code.toByte(),
                'H'.code.toByte(),
                'I'.code.toByte(),
                'J'.code.toByte(),
                'K'.code.toByte(),
                'L'.code.toByte(),  //
                'M'.code.toByte(),
                'N'.code.toByte(),
                'O'.code.toByte(),
                'P'.code.toByte(),
                'Q'.code.toByte(),
                'R'.code.toByte(),
                'S'.code.toByte(),
                'T'.code.toByte(),  //
                'U'.code.toByte(),
                'V'.code.toByte(),
                'W'.code.toByte(),
                'X'.code.toByte(),
                'Y'.code.toByte(),
                'Z'.code.toByte() //
            )

            var GMP_ENCODER: Base62Encoder = Base62Encoder(GMP)
            var INVERTED_ENCODER: Base62Encoder = Base62Encoder(INVERTED)
        }
    }

    /**
     * Base62解码器
     *
     * @since 5.8.0
     */
    class Base62Decoder(alphabet: ByteArray) {
        private val lookupTable: ByteArray = ByteArray('z'.code + 1)

        /**
         * 构造
         *
         * @param alphabet 字母表
         */
        init {
            for (i in alphabet.indices) {
                lookupTable[alphabet[i].toInt()] = i.toByte()
            }
        }


        fun decode(encoded: ByteArray): ByteArray {
            val prepared = translate(encoded, lookupTable)
            return convert(prepared, TARGET_BASE, STANDARD_BASE)
        }

        companion object {
            var GMP_DECODER: Base62Decoder = Base62Decoder(Base62Encoder.Companion.GMP)
            var INVERTED_DECODER: Base62Decoder = Base62Decoder(Base62Encoder.Companion.INVERTED)
        }
    }


        private const val serialVersionUID = 1L

        private const val STANDARD_BASE = 256
        private const val TARGET_BASE = 62

        //var INSTANCE: Base62Codec = Base62Codec()

        // region Private Methods
        /**
         * 按照字典转换bytes
         *
         * @param indices    内容
         * @param dictionary 字典
         * @return 转换值
         */
        private fun translate(indices: ByteArray, dictionary: ByteArray): ByteArray {
            val translation = ByteArray(indices.size)

            for (i in indices.indices) {
                translation[i] = dictionary[indices[i].toInt()]
            }

            return translation
        }

        /**
         * 使用定义的字母表从源基准到目标基准
         *
         * @param message    消息bytes
         * @param sourceBase 源基准长度
         * @param targetBase 目标基准长度
         * @return 计算结果
         */
        private fun convert(message: ByteArray, sourceBase: Int, targetBase: Int): ByteArray {
            // 计算结果长度，算法来自：http://codegolf.stackexchange.com/a/21672
            val estimatedLength = estimateOutputLength(message.size, sourceBase, targetBase)

            val out = Buffer()

            var source = message

            while (source.isNotEmpty()) {
                val quotient= Buffer()

                var remainder = 0

                for (b in source) {
                    val accumulator = (b.toInt() and 0xFF) + remainder * sourceBase
                    val digit = (accumulator - (accumulator % targetBase)) / targetBase

                    remainder = accumulator % targetBase

                    if (quotient.size > 0 || digit > 0) {
                        quotient.write(digit)
                    }
                }

                out.write(remainder)

                source = quotient.readByteArray()
            }

            // pad output with zeroes corresponding to the number of leading zeroes in the message
            var i = 0
            while (i < message.size - 1 && message[i].toInt() == 0) {
                out.write(0)
                i++
            }
            val ret = out.readByteArray()
            ret.reverse()
            return ret
        }

        /**
         * 估算结果长度
         *
         * @param inputLength 输入长度
         * @param sourceBase  源基准长度
         * @param targetBase  目标基准长度
         * @return 估算长度
         */
        private fun estimateOutputLength(inputLength: Int, sourceBase: Int, targetBase: Int): Int {
            return ceil((ln(sourceBase.toDouble()) / ln(targetBase.toDouble())) * inputLength).toInt()
        } // endregion

}
